﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Spanner
{
    public partial class UI
    {
        public static Expr<IDisposable> Subscribe<T>(ObsVar<T> x, Expr<Fn<T, Act>> f)
        {
            return new JSExpr<IDisposable>("#.subscribe(#)", UI.Ref(x), f);
        }

        public static Expr<IDisposable> Subscribe<T>(ComputedVar<T> x, Expr<Fn<T, Act>> f)
        {
            return new JSExpr<IDisposable>("#.subscribe(#)", UI.Ref(x), f);
        }

        public static Expr<IDisposable> Subscribe<T>(Expr<ObsVarRef<T>> x, Expr<Fn<T, Act>> f)
        {
            return new JSExpr<IDisposable>("#.subscribe(#)", x, f);
        }

        public static Expr<IDisposable> Subscribe<T>(Expr<ComputedVarRef<T>> x, Expr<Fn<T, Act>> f)
        {
            return new JSExpr<IDisposable>("#.subscribe(#)", x, f);
        }

        /// <summary>
        /// I'm taking a liberty here with IDisposable.  An Expr(IDisposable) in Spanner
        /// must have a method 'dispose' (not 'Dispose').
        /// </summary>
        public static Act Dispose(Expr<IDisposable> x)
        {
            return new JSAct("#.dispose()", x);
        }

        public static Expr<T> Peek<T>(ObsVar<T> x)
        {
            return JSExpr<T>("#.peek()", Ref(x));
        }

        public static Expr<T> Peek<T>(Expr<ObsVarRef<T>> x)
        {
            return JSExpr<T>("#.peek()", x);
        }

        public static Expr<T> Peek<T>(Expr<ComputedVarRef<T>> x)
        {
            return JSExpr<T>("#.peek()", x);
        }

        public static Html KoForEach<T>(Expr<T[]> xs, Func<Param<T>, Html> f)
        {
            return new KoForEachHtml<T> { Xs = xs, F = f };
        }

        internal class KoForEachHtml<T> : Html
        {
            internal ITerm Xs;
            internal Func<Param<T>, Html> F;
            internal bool UseCommentForm = true;

            internal override void WriteHtml(Context ctxt)
            {
                if (UseCommentForm)
                {
                    ctxt.WriteIndented("<!-- ko foreach: ");
                    Xs.Write(ctxt, JSorHtml.Html);
                    ctxt.WriteLine(" -->");
                }

                ctxt.Indentation++;

                var x = new Param<T> { Kind = VarKind.Param, BaseName = "$data" };

                ctxt.PushForeachObsVar(x);

                var childHtml = F(x);

                childHtml.WriteHtml(ctxt);

                ctxt.PopForeachObsVar();

                ctxt.Indentation--;

                if (UseCommentForm)
                {
                    ctxt.WriteIndentedLine("<!-- /ko -->");
                }
            }

            public override Spanner.Html WithChildren(params Spanner.Html[] children)
            {
                throw new ApplicationException("WithChildren cannot be applied to Knockout virtual elements.");
            }

            public override Html WithAttr(string key)
            {
                throw new ApplicationException("WithAttr cannot be applied to Knockout virtual elements.");
            }
        }

        public static Html KoIf(Expr<bool> cond, params Html[] children)
        {
            return new KoIfHtml { Cond = cond, Children = children };
        }

        public static Html KoIfNot(Expr<bool> cond, params Html[] children)
        {
            return new KoIfHtml { Cond = cond, IsIfNot = true, Children = children };
        }

        public static Html KoIfTruthy<T>(Expr<T> cond, params Html[] children)
        {
            return new KoIfHtml { Cond = cond, Children = children };
        }

        public static Html KoIfFalsy<T>(Expr<T> cond, params Html[] children)
        {
            return new KoIfHtml { Cond = cond, IsIfNot = true, Children = children };
        }

        internal class KoIfHtml : Html
        {
            internal bool IsIfNot = false;
            internal ITerm Cond;

            internal override void WriteHtml(Context ctxt)
            {
                ctxt.WriteIndented("<!-- ko {0}: ", IsIfNot ? "ifnot" : "if");
                Cond.Write(ctxt, JSorHtml.Html);
                ctxt.WriteLine(" -->");

                WriteChildren(ctxt);

                ctxt.WriteIndentedLine("<!-- /ko -->");
            }

            public override Spanner.Html WithChildren(params Spanner.Html[] children)
            {
                throw new ApplicationException("WithChildren cannot be applied to Knockout virtual elements.");
            }

            public override Html WithAttr(string key)
            {
                throw new ApplicationException("WithAttr cannot be applied to Knockout virtual elements.");
            }
        }

        public static AttrValue AttrValue<T>(string attrName, Expr<T> value)
        {
            return new AttrValue { Key = attrName, Value = value };
        }

        public static CssClassCondition CssClassCondition(string className, Expr<bool> condition)
        {
            return new CssClassCondition { Key = className, Value = condition };
        }

        public static DomEventHandler DomEventHandler(string eventName, Expr<Fn<Act>> proc)
        {
            return new DomEventHandler { Key = eventName.ToLower(), Value = proc };
        }

        public static DomEventHandler DomEventHandler(string eventName, Expr<Fn<bool>> proc)
        {
            return new DomEventHandler { Key = eventName.ToLower(), Value = proc };
        }

        public static DomEventHandler DomEventHandler(string eventName, Act act)
        {
            return new DomEventHandler { Key = eventName.ToLower(), Value = Proc(act) };
        }

        public static DomEventHandler DomEventHandler<EventT>(string eventName, Expr<Fn<DummyType, EventT, Act>> eventHandler)
        {
            return new DomEventHandler { Key = eventName.ToLower(), Value = eventHandler };
        }

        public static DomEventHandler DomEventHandler<EventT>(string eventName, Expr<Fn<DummyType, EventT, bool>> eventHandler)
        {
            return new DomEventHandler { Key = eventName.ToLower(), Value = eventHandler };
        }

        // XXX See the note at the end of this page: http://knockoutjs.com/documentation/style-binding.html
        public static CssStyleValue CssStyleValue<T>(string styleName, Expr<T> value)
        {
            return new CssStyleValue { Key = styleName, Value = value };
        }
    }

    public class JSKeyValue: ITerm {
        internal string Key;
        internal ITerm Value;

        void ITerm.Write(Context ctxt, JSorHtml what)
        {
            ctxt.WritePlain(Key.All(Char.IsLetter) ? Key : "\"" + Key + "\"");
            ctxt.WritePlain(": ");
            ctxt.WriteTerm(Value, what);
        }

        IEnumerable<IVar> ITerm.Dependencies()
        {
            return Value.Dependencies();
        }
    }

    public class CssClassCondition : JSKeyValue { }

    public class DomEventHandler: JSKeyValue { }

    public class CssStyleValue : JSKeyValue { }

    public class AttrValue : JSKeyValue { }

    public enum KoValueUpdateOn
    {
        KeyUp,
        KeyPress,
        AfterKeyDown
    };

    public sealed class DomEventName
    {
        public const string Blur = "Blur";
        public const string Change = "Change";
        public const string Click = "Click";
        public const string ContextMenu = "ContextMenu";
        public const string Copy = "Copy";
        public const string Cut = "Cut";
        public const string DblClick = "DblClick";
        public const string Drag = "Drag";
        public const string DragStart = "DragStart";
        public const string DragEnd = "DragEnd";
        public const string DragEnter = "DragEnter";
        public const string DragLeave = "DragLeave";
        public const string DragOver = "DragOver";
        public const string Drop = "Drop";
        public const string Error = "Error";
        public const string Focus = "Focus";
        public const string FocusIn = "FocusIn";
        public const string FocusOut = "FocusOut";
        public const string HashChange = "HashChange";
        public const string KeyDown = "KeyDown";
        public const string KeyPress = "KeyPress";
        public const string KeyUp = "KeyUp";
        public const string Load = "Load";
        public const string MouseDown = "MouseDown";
        public const string MouseEnter = "MouseEnter";
        public const string MouseLeave = "MouseLeave";
        public const string MouseMove = "MouseMove";
        public const string MouseOut = "MouseOut";
        public const string MouseOver = "MouseOver";
        public const string MouseUp = "MouseUp";
        public const string MouseWheel = "MouseWheel";
        public const string Paste = "Paste";
        public const string Reset = "Reset";
        public const string Resize = "Resize";
        public const string Scroll = "Scroll";
        public const string Select = "Select";
        public const string Submit = "Submit";
        public const string TextInput = "TextInput";
        public const string Unload = "Unload";
        public const string Wheel = "Wheel";
    }
}
